package lumis.content.core;

import lumis.content.ContentManagerFactory;
import lumis.portal.PortalException;
import lumis.portal.PortalRequestParameters;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.event.PortalEventOperationType;
import lumis.portal.event.persistence.PortalEventEntityListener;
import lumis.portal.group.GroupConfig;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.stability.StableMinor;
import lumis.portal.transaction.PortalTransactionFactory;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Map;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PreRemove;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;

/**
 * Represents a specific version of a content. A content version contains
 * publication definition, metadata and a reference to the business data
 * it relates to.
 * <p>
 * If a content contains versioning support, it should always create a new 
 * content version instead of modifying the existing one. If the content does
 * not contains versioning support, only one version will exist for each
 * {@link ContentLocale} and this version should when edited. 
 * 
 * @stereotype entity
 * @composed 1 - * ContentPublication
 * @composed 1 - * ContentAssociation
 *
 * @version $Revision: 13092 $ $Date: 2011-05-28 18:19:06 -0300 (Sat, 28 May 2011) $
 * @since 4.2.0
 */
@StableMinor(version = "6.0", sinceVersion = "4.2")
@Entity
@Table(name="lum_ContentVersion")
@GenericGenerator(name="portal-guid", strategy="lumis.portal.dao.hibernate.GuidGenerator")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@EntityListeners(value=PortalEventEntityListener.class)
@SuppressWarnings("serial")
public class ContentVersion implements Serializable
{
	/**
	 * Enum of audit types used by ContentVersion.
	 *
	 * @version $Revision: 13092 $ $Date: 2011-05-28 18:19:06 -0300 (Sat, 28 May 2011) $
	 * @since 6.0.0
	 */
	private static enum AuditType
	{
		ADD("lumis.content.core.ContentVersion#add"),
		UPDATE("lumis.content.core.ContentVersion#update"),
		DELETE("lumis.content.core.ContentVersion#delete");
		
		String value;
		
		AuditType(String value)
		{
			this.value = value;
		}
	}
	
	@Id @GeneratedValue(generator="portal-guid")
	private String id;
	
	private String itemId;
	
	private String lastModifiedBy;
	
	private Date lastModifiedDateTime;
	
	private String primaryName;
	
	@Column(updatable=false)
	private int versionNumber;

	@Type(type="text")
	private String introduction;
	
	@OneToMany(mappedBy="contentVersion", cascade=CascadeType.ALL)
	@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Collection<ContentAssociation> contentAssociations = new ArrayList<ContentAssociation>();
	
	@OneToMany(mappedBy="contentVersion", cascade=CascadeType.ALL)
	@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	private Collection<ContentPublication> publications = new ArrayList<ContentPublication>();
	
	@ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
	@JoinColumn(name="contentLocaleId", updatable=false, insertable=false, nullable=false)
	private ContentLocale contentLocale;
	
	@SuppressWarnings("unused") // this attribute is read only by JPA
	@JoinColumn(updatable=false, nullable=false)
	private String contentLocaleId;
	
	@Transient
	boolean sendDeleteClusterCommand = true;

	// -- Property Methods -- //
	
	/**
	 * Returns the identifier for this content version.
	 * @return the identifier for this content version.
	 * @since 4.2.0
	 */
	public String getId()
	{
		return id;
	}
	
	/**
	 * Returns the identifier for the business data this content version
	 * refers to.
	 * @return the identifier for the business data this content version
	 * refers to.
	 * @since 4.2.0
	 */
	public String getItemId()
	{
		return itemId;
	}
	
	/**
	 * Specifies the business data this content version refers to.
	 * @return the identifier of the business data.
	 * @since 4.2.0
	 */
	public void setItemId(String itemId)
	{
		this.itemId = itemId;
	}

	/**
	 * Sets this version's version number.
	 * 
	 * @param versionNumber
	 *            the version number.
	 * @since 6.0.0
	 */
	void setVersionNumber(int versionNumber)
	{
		this.versionNumber = versionNumber;
	}

	/**
	 * Returns this version's number.
	 * @return the version number.
	 * @since 4.2.0
	 */
	public int getVersionNumber()
	{
		return this.versionNumber;
	}

	/**
	 * Returns the identifier of the last principal that modified this version.
	 * @return the principal identifier.
	 * @since 4.2.0
	 */
	public String getLastModifiedBy()
	{
		return lastModifiedBy;
	}
	
	/**
	 * Sets the identifier of the last principal that modified this version.
	 * @param lastModifiedBy the principal identifier.
	 * @since 4.2.0
	 */
	public void setLastModifiedBy(String lastModifiedBy)
	{
		this.lastModifiedBy = lastModifiedBy;
	}
	
	/**
	 * Returns the last modification date-time for this version. 
	 * @return the last modification date-time for this version.
	 * @since 4.2.0
	 */
	public Date getLastModifiedDateTime()
	{
		return lastModifiedDateTime;
	}
	
	/**
	 * Sets the last modification date-time for this version.
	 * @param lastModifiedDateTime the date-time to set.
	 * @since 4.2.0
	 */
	public void setLastModifiedDateTime(Date lastModifiedDateTime)
	{
		this.lastModifiedDateTime = lastModifiedDateTime;
	}
	
	/**
	 * Returns the primary name for this content version.
	 * @return the primary name.
	 * @since 4.2.0
	 */
	public String getPrimaryName()
	{
		return primaryName;
	}
	
	/**
	 * Sets the primary name for this content version.
	 * @param primaryName the primary name.
	 * @since 4.2.0
	 */
	public void setPrimaryName(String primaryName)
	{
		this.primaryName = primaryName;
	}
	
	/**
	 * Returns the introduction for this content version.
	 * @return the introduction
	 * @since 4.2.0
	 */
	public String getIntroduction()
	{
		return introduction;
	}
	
	/**
	 * Returns the introduction for this content version.
	 * @param introduction
	 * @since 4.2.0
	 */
	public void setIntroduction(String introduction)
	{
		this.introduction = introduction;
	}
	
	/**
	 * Returns the contents associated with this version. The returned
	 * collection may be changed to edit which contents are associated with
	 * this content version.
	 * @return the contents associated with this version.
	 * @since 4.2.0
	 */
	public Collection<ContentAssociation> getContentAssociations()
	{
		return Collections.unmodifiableCollection(contentAssociations);
	}
	
	public ContentAssociation addContentAssociation()
	{
		ContentAssociation contentAssociation = new ContentAssociation(this);
		contentAssociations.add(contentAssociation);
		
		return contentAssociation;
	}
	
	void removeContentAssociation(ContentAssociation contentAssociation)
	{
		contentAssociations.remove(contentAssociation);
	}
	
	/**
	 * Returns the publications defined for this version.
	 * @return an unmodifiable view of this version's publications.
	 * @see #addPublication()
	 * @see ContentPublication#delete()
	 * @since 4.2.0
	 */
	public Collection<ContentPublication> getPublications()
	{
		return Collections.unmodifiableCollection(publications);
	}
	void removeContentPublication(ContentPublication contentPublication)
	{
		publications.remove(contentPublication);
	}
	
	/**
	 * Returns the content-locale object this version belongs to.
	 * @return the content-locale object.
	 * @since 4.2.0
	 */
	public ContentLocale getContentLocale()
	{
		return contentLocale;
	}

	void setContentLocale(ContentLocale contentLocale)
	{
		this.contentLocale = contentLocale;
		this.contentLocaleId = contentLocale.getId();
	}
	
	// -- Operations -- //
	
	/**
	 * Adds a new publication for this version.
	 * @return the new publication added.
	 * @see #getPublications()
	 * @since 4.2.0
	 */
	public ContentPublication addPublication()
	{
		ContentPublication publication = new ContentPublication();
		publication.getServiceInstanceIds().add(getContentLocale().getContent().getServiceInstanceId());
		publication.getPrincipalIds().add(GroupConfig.GROUP_ALL_USERS_ID);
		publication.setContentVersion(this);
		publications.add(publication);
		return publication;
	}

	/**
	 * Deletes this {@link ContentVersion version}. 
	 *
	 * @since 6.0.0
	 */
	public void delete() throws PortalException
	{
		getContentLocale().removeVersion(this);
		ManagerFactory.getEntityManager().remove(this);
	}

	// -- Events -- //
	
	@PreRemove
	void doPreRemove() throws PortalException
	{
		// send render data changed delete event
		ContentRenderDataChangedEvent event = new ContentRenderDataChangedEvent(
				SessionConfig.getCurrentSessionConfig(), this, PortalTransactionFactory.getCurrentTransaction());
		event.setOperationType(PortalEventOperationType.DELETE);
		Map<String, String[]> parameters = Collections.singletonMap(
				PortalRequestParameters.PAGE_PARAMETER_ITEM_ID, new String[] { getContentLocale().getContent().getId() });
		event.setParameters(parameters);
		ManagerFactory.getPortalEventManager().notifyObservers(event);
	}	
	
	@PostPersist
	void doPostPersist() throws PortalException
	{
		String newLine = "\r\n";
		String operation = "Added content version: ";
		String title = ((this.primaryName != null) && (!this.primaryName.trim().equals("")))?this.primaryName:this.id;
		title = operation + newLine + title;
		String description = operation + newLine 
							+ "  id: " + this.id + newLine 
							+ "  primaryName: " + this.primaryName + newLine 
							+ "  versionNumber: " + this.versionNumber;
		ManagerFactoryInternal.getAuditManager().audit(title, description, AuditType.ADD.value);
	}

	@PostUpdate
	void doPostUpdate() throws PortalException
	{
		String newLine = "\r\n";
		String operation = "Updated content version: ";
		String title = ((this.primaryName != null) && (!this.primaryName.trim().equals("")))?this.primaryName:this.id;
		title = operation + newLine + title;
		String description = operation + newLine 
								+ "  id: " + this.id + newLine
								+ "  primaryName: " + this.primaryName + newLine 
								+ "  versionNumber: " + this.versionNumber;
		ManagerFactoryInternal.getAuditManager().audit(title, description, AuditType.UPDATE.value);
	}
	
	@PostRemove
	void doPostRemove() throws PortalException
	{
		String newLine = "\r\n";
		String operation = "Deleted content version: ";
		String title = ((this.primaryName != null) && (!this.primaryName.trim().equals("")))?this.primaryName:this.id;
		title = operation + newLine + title;
		String description = operation + newLine 
							+ "  id: " + this.id + newLine 
							+ "  primaryName: " + this.primaryName + newLine 
							+ "  versionNumber: " + this.versionNumber;
		ManagerFactoryInternal.getAuditManager().audit(title, description, AuditType.DELETE.value);
		sendDeleteNotification();
	}

	/**
	 * Sends a &lt;itemId, contentLocaleId&gt; mapping over the cluster.
	 * 
	 * @since 6.0.0
	 */
	private void sendDeleteNotification() throws PortalException
	{
		if (sendDeleteClusterCommand)
			((ContentManager) ContentManagerFactory.getContentManager()).sendContentVersionDeletedClusterCommand(
					itemId, getContentLocale().getId());
	}
}